package com.xjm.i1.core;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.lang.Assert;
import com.xjm.i1.Exceptions.AreaTypeNotFoundException;
import com.xjm.i1.constant.ExtraConfigConstant;
import com.xjm.i1.core.context.ApplicationContextHolder;
import com.xjm.i1.core.context.MetaDataLoader;
import com.xjm.i1.core.context.PropertySourceLoader;
import com.xjm.i1.core.env.ExtraConfig;
import com.xjm.i1.core.env.ExtraConfigPropertySource;
import com.xjm.i1.core.env.PropertySource;
import com.xjm.i1.core.generator.ExcelGenerator;
import com.xjm.i1.core.resolver.MetaDataResolveAdapter;
import com.xjm.i1.core.struct.node.load.I1AreaCollections;
import com.xjm.i1.core.struct.node.load.I1DataCollections;
import com.xjm.i1.core.struct.node.load.I1DeviceCollections;
import com.xjm.i1.core.struct.node.load.I1PageCollections;
import com.xjm.i1.utils.StrUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class XJMApplication {

    private final PropertySourceLoader propertySourceLoader;

    private final MetaDataLoader metaDataLoader;

    private PropertySource<ExtraConfig> extraConfigPropertySource;

    private boolean COVER_REPEAT_DATA;

    private final ExcelGenerator excelGenerator;

    private final ClassPathResource bannerResource = new ClassPathResource("banner.txt");

    public static XJMApplication run(String[] args) {
        return new XJMApplication(args);
    }

    public XJMApplication(String...args) {
        bannerPrint();
        propertySourceLoader = new PropertySourceLoader(args);
        excelGenerator = new ExcelGenerator();
        metaDataLoader = new MetaDataLoader();
        ApplicationContextHolder.setMetaDataResolveAdapter(new MetaDataResolveAdapter());
        ApplicationContextHolder.setPropertySourceLoader(propertySourceLoader);
        this.run();
    }

    public void run() {
        // 构建顶层结构，并加载I1扫描仪数据
        I1DataCollections i1Data = buildI1DataCollections();
        ApplicationContextHolder.setI1DataCollections(i1Data);
        loadI1Data(i1Data);

        // 生成数据并导出
        excelGenerator.convertExcelData(i1Data);
        excelGenerator.generateExcelAndOutPut(i1Data, getExcelTemplateConfig());
    }

    /**
    * 检索banner并打印到控制台
    * */
    private void bannerPrint() {
        try {
            String banner = StrUtils.copyToString(bannerResource.getStream(), StandardCharsets.UTF_8);
            System.out.println(banner);
        } catch (IOException exception) {
            log.error(String.format("获取banner数据失败！跳过显示[%s]", exception.getMessage()), exception);
        }
        System.out.println(":: author ::     >> alexYang");
    }



    /**
    * 加载xjm.config.input.path配置文件夹中的所有数据，并存入参数i1Data中
    * @param i1Data 顶层数据体
    * */
    private void loadI1Data(I1DataCollections i1Data) {
        log.info("========== 开始读出数据 ==========");
        // 加载是否覆盖重复数据
        ExtraConfig source = this.extraConfigPropertySource.getSource();
        boolean isContain = source.containsOption(ExtraConfigConstant.COVER_REPEAT_DATA);
        if (isContain) {
            String coverRepeat = source.getOptionValues(ExtraConfigConstant.COVER_REPEAT_DATA).get(0);
            this.COVER_REPEAT_DATA = Boolean.valueOf(coverRepeat);
        } else {
            this.COVER_REPEAT_DATA = false;
        }
        log.info(String.format("检测到覆盖冲突命名开关为[%b]", this.COVER_REPEAT_DATA));

        File[] dataFiles = new File(i1Data.getInputPath()).listFiles();
        Assert.notEmpty(dataFiles, String.format("检测到设置项[%s]中无数据", ExtraConfigConstant.INPUT_PATH));

        // 第一层可获取所有机器编号+页面编号目录 机器编号_页面编号
        for (File dataFile : dataFiles) {
            log.debug(String.format("检测到文件标识[%s]", dataFile.getAbsolutePath()));
            if (dataFile.isDirectory()) {
                log.debug(String.format("解析文件标识[%s]", dataFile.getAbsolutePath()));
                Assert.isTrue(checkIsConfigDir(dataFile),
                        String.format("匹配到不符合命名方式的文件夹[%s], 请按照格式重新配置或移除不相关的文件夹[%s]",
                                dataFile.getAbsolutePath(), "字母数字符号#- 1-12位 + _ + 字母数字符号#- 1-12位"));

                String[] dev_page = dataFile.getName().split("_");
                I1DeviceCollections deviceCollections = i1Data.getOrNewDeviceCollections(dev_page[0]);
                I1PageCollections pageCollections = deviceCollections.getOrNewPageCollections(dev_page[1]);

                File[] pageFiles = dataFile.listFiles();
                log.debug(String.format("解析文件目录[%s]中存在文件[%s]", dataFile.getAbsolutePath(), Arrays.toString(pageFiles)));
                Assert.notEmpty(pageFiles, String.format("检测到数据输入文件夹中无数据读取[%s]", dataFile.getAbsolutePath()));

                // 第二层可获取当前页的所有区域数据
                for (File pageFile : pageFiles) {
                    log.debug(String.format("检测到文件标识[%s]", pageFile.getAbsolutePath()));
                    Assert.isTrue(pageFile.isFile(), String.format("检测到区域数据中存在文件夹[%s]", pageFile.getAbsolutePath()));
                    if (!FileUtil.getSuffix(pageFile).equals(I1PageCollections.DATA_SUFFIX)) {
                        log.info(String.format("[%s]文件非.txt结尾文件，跳过解析",pageFile.getAbsolutePath()));
                        continue;
                    }
                    String areaFileName = pageFile.getName();
                    if (areaFileName.equals(I1PageCollections.ENV_NAME)) {
                        log.debug(String.format("检测到环境配置文件[%s]", pageFile.getAbsolutePath()));
                        String env = FileUtil.readLines(pageFile, StandardCharsets.UTF_8).toString();
                        pageCollections.setEnv(env);
                    } else {
                        try {
                            log.debug(String.format("开始解析数据文件[%s]", pageFile.getAbsolutePath()));
                            I1AreaCollections load = this.metaDataLoader.load(pageFile);
                            pageCollections.getAreaCollections().add(load);
                        } catch (AreaTypeNotFoundException e) {
                            log.error(String.format("[%s]文件加载失败！错误：%s,即将跳过进行下一个文件解析", pageFile.getAbsolutePath(), e.getMessage()), e.getStackTrace());
                        }
                    }
                }

                // 设备中已经存有对应页数编号的数据
                if (deviceCollections.containsKey(dev_page[1])) {
                    if (this.COVER_REPEAT_DATA) {
                        log.info(String.format("检查到设备[%s]存在相同页面编号[%s]的数据， %s设置为%b，将覆盖此条数据",
                                dev_page[0], dev_page[1], ExtraConfigConstant.COVER_REPEAT_DATA, this.COVER_REPEAT_DATA));
                        deviceCollections.put(dev_page[1], pageCollections);
                    } else {
                        log.info(String.format("检查到设备[%s]已经存在相同页面编号[%s]的数据， %s设置为%b，将放弃覆盖此条数据",
                                dev_page[0], dev_page[1], ExtraConfigConstant.COVER_REPEAT_DATA, this.COVER_REPEAT_DATA));
                    }
                } else {
                    deviceCollections.put(dev_page[1], pageCollections);
                }

                i1Data.put(dev_page[0], deviceCollections);
            }
        }
        log.info("========== 读出数据完成 ==========");
    }

    /**
     * 获取外部配置或默认的excel导出模板
     * */
    private InputStream getExcelTemplateConfig() {
        List<String> optionValues = this.extraConfigPropertySource.getSource().getOptionValues(ExtraConfigConstant.EXPORT_EXCEL_TEMPLATE);
        if (CollUtil.isEmpty(optionValues)) {
            log.debug(String.format("未检测到自定义配置的Excel导出模板，将使用默认模板：[%s]", ExcelGenerator.DEFAULT_TEMPLATE_PATH));
            return ExcelGenerator.DEFAULT_TEMPLATE_PATH.getStream();
        } else {
            String excelTemplatePath = optionValues.get(0);
            try {
                InputStream inputStream = new FileInputStream(new File(excelTemplatePath));
                Assert.isTrue(ExcelGenerator.SUPPORT_SUFFIX.equals(FileUtil.getSuffix(excelTemplatePath)), String.format("检测到外置设置配置[%s], 请检查配置文件是否为xlsx文件",
                        ExtraConfigConstant.EXPORT_EXCEL_TEMPLATE));
                return inputStream;
            } catch (FileNotFoundException exception) {
                Assert.isTrue(FileUtil.isFile(excelTemplatePath), String.format("检测到外置设置配置[%s], 请检查配置路径是否正确或是否为文件[%s]",
                        ExtraConfigConstant.EXPORT_EXCEL_TEMPLATE, excelTemplatePath));
                System.exit(-1);
            }
            log.debug(String.format("检测到自定义配置的Excel导出模板，将使用配置模板：[%s]", excelTemplatePath));
        }
        return null;
    }

    /**
     * 匹配文件夹名称格式 字母数字符号#- 1-12位 + _ + 字母数字符号#- 1-12位
     * @param dir 文件夹File对象
     * @return boolean 是否匹配
     * */
    private boolean checkIsConfigDir(File dir) {
        return dir.getName().matches("^[a-zA-Z0-9#-]{1,12}_[a-zA-Z0-9#-]{1,12}$");
    }

    /**
     * 根据配置文件构造顶层数据结构
     * */
    private I1DataCollections buildI1DataCollections() {
        String inputPath = getInputOrOutputPath(ExtraConfigConstant.INPUT_PATH);
        String outputPath = getInputOrOutputPath(ExtraConfigConstant.OUTPUT_PATH);
        String generateName = FileUtil.getPrefix(inputPath);
        log.debug(String.format("构造顶层加载数据结构 >> 数据文件夹名[%s], 输入文件夹解析路径[%s], 输出文件夹数据路径[%s]",
                generateName, inputPath, outputPath));
        return new I1DataCollections(generateName, inputPath, outputPath);
    }

    /**
     * 获取input或output的key获取外置配置文件信息中的值
     * */
    private String getInputOrOutputPath(String key) {
        // 获取外置配置文件信息
        extraConfigPropertySource = (PropertySource<ExtraConfig>) this.propertySourceLoader
                .getPropertySourceByName(ExtraConfigPropertySource.name);
        List<String> optionValues = extraConfigPropertySource.getSource().getOptionValues(key);
        log.debug(String.format("外部配置文件获取key[%s], values:[%s]", key, optionValues.toString()));
        Assert.notEmpty(optionValues, String.format("外部配置文件中参数[%s]不能为空", key));
        String dirPath = optionValues.get(0);
        // 根据文件夹解析
        Assert.isTrue(FileUtil.isDirectory(dirPath), String.format("外部配置文件中参数[%s]必须为一个文件夹,path:[%s]", key, dirPath));
        return dirPath;
    }

}
